--------------Congo Bongo--------------
A 4am crack                  2018-09-22
---------------------------------------

Name: Congo Bongo
Genre: action
Year: 1983
Publisher: SEGA Enterprises
Platform: Apple ][+ or later
Media: 5.25-inch disk
Sides: 1
OS: custom

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  immediate disk read error

Locksmith Fast Disk Backup
  unable to read any track

EDD 4 bit copy (no sync, no count)
  errors on tracks $01, $21, $22
  copy crashes shortly after boot

Copy ][+ nibble editor
  Tracks $01, $21, and $22 appear to be
  unformatted, but I suspect one of
  them is actually a cleverly disguised
  protection track.
  Other tracks are only lightly
  protected with a modified epilogue
  ($DE $A9 $EB instead of $DE $AA $EB)

Disk Fixer
  with the proper epilogue, I can read
  all sectors on all tracks other than
  tracks $01, $21, and $22

Passport
  converts disk to standard format
  (normalizing the epilogues) but no
  patches

So we're going to do this the old
fashioned way. Excellent.

Why didn't COPYA work?
  modified epilogues

Why didn't Locksmith FDB work?
  modified epilogues

Why didn't my EDD copy work?
  I don't know. Probably a runtime
  check against one of those unreadable
  tracks.

Next steps:

  1. Trace the boot
  2. Find and disable the runtime check
  3. Declare victory (*)

(*) go to the gym

                   ~

               Chapter 1
      In Which We Brag About Our
           Humble Beginnings


I have two floppy drives, one in slot 6
and the other in slot 5. My "work disk"
(in slot 5) runs Diversi-DOS 64K, which
is compatible with Apple DOS 3.3 but
relocates most of DOS to the language
card on boot. This frees up most of
main memory (only using a single page
at $BF00..$BFFF), which is useful for
loading large files or examining code
that lives in areas typically reserved
for DOS.

[S6,D1=original disk]
[S5,D1=my work disk]

The floppy drive firmware code at $C600
is responsible for aligning the drive
head and reading sector 0 of track 0
into main memory at $0800. Because the
drive can be connected to any slot, the
firmware code can't assume it's loaded
at $C600. If the floppy drive card were
removed from slot 6 and reinstalled in
slot 5, the firmware code would load at
$C500 instead.

To accommodate this, the firmware does
some fancy stack manipulation to detect
where it is in memory (which is a neat
trick, since the 6502 program counter
is not generally accessible). However,
due to space constraints, the detection
code only cares about the lower 4 bits
of the high byte of its own address.

Stay with me, this is all about to come
together and go boom.

$C600 (or $C500, or anywhere in $Cx00)
is read-only memory. I can't change it,
which means I can't stop it from
transferring control to the boot sector
of the disk once it's in memory. BUT!
The disk firmware code works unmodified
at any address. Any address that ends
with $x600 will boot slot 6, including
$B600, $A600, $9600, &c.

; copy drive firmware to $9600
*9600<C600.C6FFM

; and execute it
*9600G
...reboots slot 6, loads game...

Now then:

]PR#5
...
]CALL -151

*9600<C600.C6FFM

*96F8L

96F8-   4C 01 08    JMP   $0801

That's where the disk controller ROM
code ends and the on-disk code begins.
But $9600 is part of read/write memory.
I can change it at will. So I can
interrupt the boot process after the
drive firmware loads the boot sector
from the disk but before it transfers
control to the disk's bootloader.

; instead of jumping to on-disk code,
; copy boot sector to higher memory so
; it survives a reboot
96F8-   A0 00       LDY   #$00
96FA-   B9 00 08    LDA   $0800,Y
96FD-   99 00 28    STA   $2800,Y
9700-   C8          INY
9701-   D0 F7       BNE   $96FA

; turn off slot 6 drive motor
9703-   AD E8 C0    LDA   $C0E8

; reboot to my work disk in slot 5
9706-   4C 00 C5    JMP   $C500

*BSAVE TRACE,A$9600,L$109
*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE OBJ.0800-08FF,A$2800,L$100

Now we get to(*) trace the boot process
one sector, one page, one instruction
at a time.

(*) If you replace the words "need to"
    with the words "get to," life
    becomes amazing.

                   ~

               Chapter 2
         Boot Trace and Chill


]CALL -151

; move boot0 back into place
*800<2800.28FFM

*801L

; looks like DOS 3.3 bootloader
0801-   A5 27       LDA   $27
0803-   C9 09       CMP   #$09
0805-   D0 0E       BNE   $0815

; first time through, set up vector
; to $Cx5C, a standard entry point
; inside the Disk II drive firmware
; to read a sector
0807-   A5 2B       LDA   $2B
0809-   4A          LSR
080A-   4A          LSR
080B-   4A          LSR
080C-   4A          LSR
080D-   09 C0       ORA   #$C0
080F-   85 3F       STA   $3F
0811-   A9 5C       LDA   #$5C
0813-   85 3E       STA   $3E

; except the number of sectors to read
; from track 0 is in $086D
0815-   AE 6D 08    LDX   $086D

; branch when all sectors are read
0818-   30 15       BMI   $082F
081A-   BD 5A 08    LDA   $085A,X
081D-   85 3D       STA   $3D
081F-   CE 6D 08    DEC   $086D

; initial memory page is in $086C
0822-   AD 6C 08    LDA   $086C
0825-   85 27       STA   $27

; then decremented
0827-   CE 6C 08    DEC   $086C

; call drive firmware to read another
; sector from track 0
082A-   A6 2B       LDX   $2B
082C-   6C 3E 00    JMP   ($003E)

; execution continues here (from $0818)
; after all sectors are read
082F-   20 21 BA    JSR   $BA21   <-- ?

; standard machine initialization stuff
0832-   20 89 FE    JSR   $FE89
0835-   20 93 FE    JSR   $FE93
0838-   20 2F FB    JSR   $FB2F

; set some page 3 vectors
083B-   A9 2E       LDA   #$2E
083D-   8D D7 03    STA   $03D7
0840-   A9 B6       LDA   #$B6
0842-   8D D8 03    STA   $03D8
0845-   A9 4C       LDA   #$4C
0847-   8D D6 03    STA   $03D6
084A-   A9 00       LDA   #$00
084C-   8D D9 03    STA   $03D9
084F-   A9 B6       LDA   #$B6
0851-   8D DA 03    STA   $03DA
0854-   20 2E B6    JSR   $B62E   <-- ?

; probably game initialization code
0857-   4C 00 0C    JMP   $0C00

*86C.86D

086C- BF 0A

The initial boot is a loop (because
$C65C exits via $0801) which loads
$0B sectors ($0A + 1) into $B500..$BFFF
then branches to $082F. There I see two
subroutine calls, to $BA21 and $B62E,
then we're jumping into code at $0C00
that isn't loaded by this initial loop.

Let's interrupt the boot at $082F and
see what's happening at $BA21.

                   ~

               Chapter 3
          Do You Want To Know
         How I Got These Bits?


*9600<C600.C6FFM

; modify initial bootloader to jump to
; an address under our control after it
; finishes reading sectors with the
; drive firmware
96F8-   A9 4C       LDA   #$4C
96FA-   8D 2F 08    STA   $082F
96FD-   A9 0A       LDA   #$0A
96FF-   8D 30 08    STA   $0830
9702-   A9 97       LDA   #$97
9704-   8D 31 08    STA   $0831

; start the boot
9707-   4C 01 08    JMP   $0801

; our callback is here --
; copy everything read from track 0 to
; lower memory so it survives a reboot
970A-   A2 0B       LDX   #$0B
970C-   A0 00       LDY   #$00
970E-   B9 00 B5    LDA   $B500,Y
9711-   99 00 25    STA   $2500,Y
9714-   C8          INY
9715-   D0 F7       BNE   $970E
9717-   EE 10 97    INC   $9710
971A-   EE 13 97    INC   $9713
971D-   CA          DEX
971E-   D0 EE       BNE   $970E

; turn off the drive motor and reboot
; to my work disk
9720-   AD E8 C0    LDA   $C0E8
9723-   4C 00 C5    JMP   $C500

*BSAVE TRACE2,A$9600,L$2500,L$B00
*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE OBJ.B500-BFFF,A$2500,L$B00
]CALL -151

; move the captured code into place
; (but don't overwrite $BF00+ because
; my work disk's DOS lives there)
*B500<2500.2EFFM

Now let's see what's at $BA21.

*BA21L

BA21-   A9 02       LDA   #$02
BA23-   A6 2B       LDX   $2B
BA25-   8E 1F B6    STX   $B61F

; seek to phase given in A (not shown,
; but each track is 2 phases, so we're
; going to the unreadable track $01)
BA28-   20 95 B8    JSR   $B895

BA2B-   20 71 BA    JSR   $BA71

*BA71L

; loop forever until we find an $FF
; nibble
BA71-   A0 00       LDY   #$00
BA73-   BD 8C C0    LDA   $C08C,X
BA76-   10 FB       BPL   $BA73
BA78-   C9 FF       CMP   #$FF
BA7A-   D0 F7       BNE   $BA73
BA7C-   88          DEY

; count following $FF nibbles (Y holds
; count, but isn't actually used)
BA7D-   BD 8C C0    LDA   $C08C,X
BA80-   10 FB       BPL   $BA7D
BA82-   C9 FF       CMP   #$FF
BA84-   F0 F6       BEQ   $BA7C
BA86-   EA          NOP

; followed by a $92 nibble
BA87-   C9 92       CMP   #$92
BA89-   D0 E6       BNE   $BA71

; store next two nibbles, one in Y and
; one in A
BA8B-   20 9C BA    JSR   $BA9C
BA8E-   BD 8C C0    LDA   $C08C,X
BA91-   10 FB       BPL   $BA8E
BA93-   A8          TAY
BA94-   20 9C BA    JSR   $BA9C
BA97-   BD 8C C0    LDA   $C08C,X
BA9A-   10 FB       BPL   $BA97
BA9C-   60          RTS

; store the two nibbles we found at the
; end of the subroutine (after the
; $FF $FF $92 sequence)
BA2E-   8C 00 02    STY   $0200
BA31-   8D 01 02    STA   $0201
BA34-   20 67 BA    JSR   $BA67

*BA67L

; wait a specific amount of time
BA67-   A9 FF       LDA   #$FF
BA69-   20 A8 FC    JSR   $FCA8
BA6C-   A9 60       LDA   #$60
BA6E-   20 A8 FC    JSR   $FCA8

...then we fall through to the same
subroutine at $BA71, so we'll look for
the $FF $FF $92 sequence a second time
and capture the next two nibbles again.

; store those two nibbles, but in a
; slightly different place
BA37-   8C 02 02    STY   $0202
BA3A-   8D 03 02    STA   $0203

; and a third time (including waiting)
BA3D-   20 67 BA    JSR   $BA67

; compare the nibbles we got the third
; time to the nibbles we got the first
; and second times
BA40-   CC 00 02    CPY   $0200
BA43-   D0 21       BNE   $BA66
BA45-   CC 02 02    CPY   $0202
BA48-   D0 1C       BNE   $BA66
BA4A-   CD 01 02    CMP   $0201
BA4D-   D0 17       BNE   $BA66
BA4F-   CD 03 02    CMP   $0203
BA52-   D0 12       BNE   $BA66

; all nibbles were the SAME each time,
; and that's BAD (what) --
; wipe memory and halt
BA54-   A0 08       LDY   #$08
BA56-   84 01       STY   $01
BA58-   A0 00       LDY   #$00
BA5A-   84 00       STY   $00
BA5C-   91 00       STA   ($00),Y
BA5E-   C8          INY
BA5F-   D0 FB       BNE   $BA5C
BA61-   E6 01       INC   $01
BA63-   D0 F7       BNE   $BA5C

; on 6502 CPUs, opcode $02 will halt
; the machine
BA65-   02          ???

; success path is here, if any nibbles
; were DIFFERENT when we read them
; again -- quietly return to the caller
BA66-   60          RTS

This is definitely a protection check.
It's reading values from the unreadable
track $01, three times in a row, and
expecting the values to change.

Floppy disk got some explainin' to do.

                   ~

               Chapter 4
     Now You See It, Now You Don't


There's only one thing you can put on a
disk that will change every time you
read it: nothing. And by "nothing," I
mean "a long sequence of zero bits."
And that's what is on the original disk
between each of these long groups of
nibbles: nothing.

A bit of background. When we say a
"zero bit," we really mean "the lack of
a magnetic state change." The Disk II
drive isn't digital; it's analog. If it
doesn't see a state change in a certain
period of time, it calls that a "0". If
it does see a change, it calls that a
"1". But the drive can only tolerate a
lack of state changes for so long --
about as long as it takes for two bits
to go by.

Fun fact(*): this is why disks use
nibbles as an intermediate on-disk
format in the first place. No valid
nibble contains more than two zero bits
consecutively, when written from most-
significant to least-significant bit.

(*) not guaranteed, actual fun may vary

So what happens when a drive doesn't
see a state change after the equivalent
of two consecutive zero bits? The drive
thinks the disk is weak, and the MC3470
chip inside the drive starts increasing
the amplification to try to compensate,
looking for a valid signal.  But there
is no signal. There is no data. There
is just a yawning abyss of nothingness.
Eventually, the drive gets desperate
and amplifies so much that it starts
returning random bits based on ambient
noise from the disk motor and the
magnetism of the Earth.

Seriously.

It's trivial to write zero bits to a
disk; just write a #$00 nibble to write
8 zero bits -- like any other 8-bit
nibble. You can write whatever you want
to a disk; it doesn't need to be what
DOS would consider a "valid" nibble.
But when you read that nibble back, the
drive can't handle 8 zero bits in a
row, so it will actually return some
random bits. Which is why no one does
that.

Returning random bits doesn't sound
very useful for a storage device, but
it's exactly what the developer wanted,
and that's exactly what this copy
protection scheme depends on. Here's
why:

Bit copiers can't duplicate a long
sequence of zero bits.

Why? Because that's not what they see.
What they see is some random bits --
the real zero bits interspersed with
phantom "1" bits. So that's what they
write to the target disk. Whatever
randomness they get when they read the
original disk will essentially get
"frozen" onto the copy.

And track 1 is nothing but a few $FF
nibbles, a single $92 nibble, and a
yawning abyss of zero bits.

Simple to create. Impossible to copy.

                   ~

               Chapter 5
            We Patch Along


This protection check doesn't have any
signals for success; if it exits, the
check has succeeded. That means I can
bypass it by putting an "RTS" at the
beginning:

T00,S05,$21: A9 -> 60

Now the boot gets slightly further,
swinging out to a higher track before
grinding the disk and crashing.

Which means I get to patch the RWTS so
it can read itself.

Returning to my work disk and restoring
the captured track 0 code to $B500+, I
go spelunking and find a DOS-shaped
RWTS except everything is in the wrong
place.

*B7FEL

; match data field epilogue
B7FE-   BD 8C C0    LDA   $C08C,X
B801-   10 FB       BPL   $B7FE
B803-   C9 DE       CMP   #$DE
B805-   D0 14       BNE   $B81B
B807-   EA          NOP
B808-   BD 8C C0    LDA   $C08C,X
B80B-   10 FB       BPL   $B808

; I can change this $A9 to $AA
B80D-   C9 A9       CMP   #$A9    <-- !
B80F-   F0 08       BEQ   $B819

*B864L

; match address field epilogue
B864-   BD 8C C0    LDA   $C08C,X
B867-   10 FB       BPL   $B864
B869-   C9 DE       CMP   #$DE
B86B-   D0 AE       BNE   $B81B
B86D-   EA          NOP
B86E-   BD 8C C0    LDA   $C08C,X
B871-   10 FB       BPL   $B86E

; I can change this $A9 to $AA
B873-   C9 A9       CMP   #$A9    <-- !
B875-   D0 A4       BNE   $B81B
B877-   EA          NOP

; match all three epilogue nibbles
; after the address field (unusual)
B878-   BD 8C C0    LDA   $C08C,X
B87B-   10 FB       BPL   $B878
B87D-   C9 EB       CMP   #$EB    <-- ?
B87F-   D0 9A       BNE   $B81B

Let's patch the RWTS so it can read
standard epilogues, and also let it
ignore the third epilogue nibble after
the address field. (The final "release"
of this crack will be a .dsk file, so
it will be up to emulators to recreate
the standard structure of the disk.
Guess what? Some of them recreate only
two nibbles after the address epilogue,
because that's all standard DOS 3.3
requires.)

; normalize second epilogue nibbles
T00,S03,$0E: A9 -> AA
T00,S03,$74: A9 -> AA

; ignore third address epilogue nibble
T00,S03,$80: 9A -> 00

Rebooting with these RWTS patches in
place should allow the disk to read
itself, stop the grinding, and load the
game.

]PR#6
...boots, crashes...

I have missed something.

                   ~

               Chapter 6
          The Missing Manual


It turns out the RWTS doesn't just try
to match three epilogue nibbles after
the address field. Immediately after
matching those three nibbles, we see
this weirdness:

; intentionally burning some CPU
; cycles (7 of them) with pointless
; stack manipulation
B881-   48          PHA
B882-   68          PLA

; store next two nibbles in zero page
B883-   BD 8C C0    LDA   $C08C,X
B886-   10 FB       BPL   $B883
B888-   85 06       STA   $06
B88A-   EA          NOP
B88B-   EA          NOP
B88C-   BD 8C C0    LDA   $C08C,X
B88F-   10 FB       BPL   $B88C
B891-   85 07       STA   $07
B893-   18          CLC
B894-   60          RTS

A quick search for "A5 06" (loading the
accumulator with zero page $06) finds
a routine at -- dun dun dun -- $B62E,
which is called from the bootloader (at
$0854):

*B62EL

; From what I can gather, $B623 is the
; sector number, $B622 is the track
; number, and $B624/25 is the address.
; So we're reading T0A,S00 into $BA21,
; overwriting the protection routine
; that was there earlier.
B62E-   A9 00       LDA   #$00
B630-   8D 23 B6    STA   $B623
B633-   85 10       STA   $10
B635-   A9 0A       LDA   #$0A
B637-   8D 22 B6    STA   $B622
B63A-   A9 21       LDA   #$21
B63C-   8D 24 B6    STA   $B624
B63F-   A9 BA       LDA   #$BA
B641-   8D 25 B6    STA   $B625

; call RWTS to do a read
B644-   20 1B B9    JSR   $B91B

; Now check those zero page addresses
; that were set from nibbles after the
; address epilogue (in $06/$07), and
; make sure they differ from their
; previous values (in $08/$09, although
; I'm not sure where these are set).
B647-   A5 06       LDA   $06
B649-   C5 08       CMP   $08
B64B-   D0 11       BNE   $B65E
B64D-   A5 07       LDA   $07
B64F-   C5 09       CMP   $09
B651-   D0 0B       BNE   $B65E

; If the nibbles-after-epilogue are
; the same as the previous read, try
; again, up to 8 times, but finally
; give up and halt.
B653-   A5 10       LDA   $10
B655-   69 00       ADC   #$00
B657-   85 10       STA   $10
B659-   C9 08       CMP   #$08
B65B-   D0 E7       BNE   $B644

; halt (6502 only)
B65D-   02          ???

This is the same kind of protection we
saw earlier on track 1, but this time
the 0-bits-that-randomly-become-1-bits
are spread throughout the entire disk.
They're literally everywhere: every
track, every sector, after every
epilogue, and reading the randomness
is baked into the RWTS.

That seems a bit excessive, honestly.

At any rate, this protection check is
also easy to bypass. At $B647,
immediately after the RWTS returns, I
can branch to $B65E and skip the
zero page comparisons altogether.

; if carry is clear after RWTS (which
; it should be, or we have bigger
; problems), branch to success path
T00,S01,$47: A506 -> 9015

]PR#6
...works, and it is glorious...

Quod erat liberandum.

---------------------------------------
A 4am crack                    No. 1818
------------------EOF------------------
